#include <sys/types.h>
#include <sys/time.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <time.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <assert.h>
#include <signal.h>
#include <stdbool.h>

#include <err.h>
#include <event.h>
#include <evhttp.h>

#include "storefile.h"
#include "trie.h"

struct settings {
    int port;
    char *interface;
	int timeout;
	
	int maxconns;

	int isstore;
	char *storefile;
	
	char charset[10];
};

struct settings settings;

void settings_init(void) {
    settings.port = 80;
    settings.interface = "0.0.0.0";
	settings.timeout = 1;

    settings.maxconns = 1024*5;
	
	settings.isstore = 0;
	settings.storefile = NULL;
	
	strcpy(settings.charset, "utf-8");
}

char *urldecode(char *input_str) 
{
		int len = strlen(input_str);
		char *str = strdup(input_str);
		
        char *dest = str; 
        char *data = str; 

        int value; 
        int c; 

        while (len--) { 
                if (*data == '+') { 
                        *dest = ' '; 
                } 
                else if (*data == '%' && len >= 2 
							&& isxdigit((int) *(data + 1)) 
							&& isxdigit((int) *(data + 2))) { 

                        c = ((unsigned char *)(data+1))[0]; 
                        if (isupper(c)) 
                                c = tolower(c); 
                        value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16; 
                        c = ((unsigned char *)(data+1))[1]; 
                        if (isupper(c)) 
                                c = tolower(c); 
                                value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10; 

                        *dest = (char)value ; 
                        data += 2; 
                        len -= 2; 
                } else { 
                        *dest = *data; 
                } 
                data++; 
                dest++; 
        } 
        *dest = '\0'; 
        return str; 
}

void httpban_handler(struct evhttp_request *req, void *arg)
{
        struct evbuffer *buf;
        buf = evbuffer_new();
		
		char *decode_uri = strdup((char*) evhttp_request_uri(req));
		char *remote_ip = strdup(req->remote_host);
		
		struct evkeyvalq httpban_http_query;
		evhttp_parse_query(decode_uri, &httpban_http_query);
		free(decode_uri);
		
		const char *httpban_input_action = evhttp_find_header(&httpban_http_query, "action");
		const char *httpban_input_data = evhttp_find_header(&httpban_http_query, "data");		
		
		evhttp_add_header(req->output_headers, "Content-Type", "text/plain");
		
		//evhttp_add_header(req->output_headers, "Keep-Alive", "120");
		evhttp_add_header(req->output_headers, "Cneonction", "close");

		if (httpban_input_action != NULL && strlen(httpban_input_action) == 3) {
			if (strcmp(httpban_input_action, "add") == 0 
					|| strcmp(httpban_input_action, "del") == 0
					|| strcmp(httpban_input_action, "ban") == 0 ) {
				int buffer_data_len;
				char *httpban_input_postbuffer;
				char *buffer_data;
				
				if (httpban_input_data != NULL) {
					buffer_data_len = strlen(httpban_input_data);
					buffer_data = (char *)malloc(buffer_data_len + 1);
					memset(buffer_data, '\0', buffer_data_len + 1);
					memcpy (buffer_data, httpban_input_data, buffer_data_len);
					httpban_input_postbuffer = urldecode(buffer_data);
				} else {
					buffer_data_len = EVBUFFER_LENGTH(req->input_buffer);
					if (buffer_data_len > 0) {
						buffer_data = (char *)malloc(buffer_data_len + 1);
						memset(buffer_data, '\0', buffer_data_len + 1);
						memcpy (buffer_data, EVBUFFER_DATA(req->input_buffer), buffer_data_len);
						httpban_input_postbuffer = urldecode(buffer_data);
					}
				}
				
				if (httpban_input_postbuffer != NULL && buffer_data_len > 0) {
					
					int state = -1;
					
					if (strcmp(httpban_input_action, "add") == 0) {
						evbuffer_add_printf(buf, "{\"status\":\"%d\"}", words_add(buffer_data));
						write_bakup_words(settings.storefile, buffer_data, "add", remote_ip);
					} else if (strcmp(httpban_input_action, "ban") == 0) {
						char *info;
						info = (char *)malloc(buffer_data_len + 35);
						memset(info, '\0', buffer_data_len + 35);
						state = words_filter(buffer_data, info);
						if(state == -1) {
							evbuffer_add_printf(buf, "{\"status\":\"%d\"}", state);
						} else {
							evbuffer_add_printf(buf, "{\"status\":\"1\",\"pos\":\"%s\"}", info);
						}
						free(info);
					} else if (strcmp(httpban_input_action, "del") == 0) {
						evbuffer_add_printf(buf, "{\"status\":\"%d\"}", words_delete(buffer_data));
						write_bakup_words(settings.storefile, buffer_data, "del", remote_ip);
					}
					
					free(httpban_input_postbuffer);
					free(buffer_data);
					
			        evhttp_send_reply(req, HTTP_OK, "OK", buf);
				}
				
			} else {
				evhttp_send_reply(req, HTTP_BADREQUEST, "BADREQUEST", buf);			
			}
		} else {
			evhttp_send_reply(req, HTTP_BADREQUEST, "BADREQUEST", buf);
		}
		
		evhttp_clear_headers(&httpban_http_query);
		evbuffer_free(buf);
		free(remote_ip);
}

static void kill_signal(const int sig) {
    exit(0);
}

void usage(void) {
    printf("-l <ip_addr>  interface to listen on, default is INDRR_ANY\n");
	printf("-p <num>      TCP port number to listen on (default: 80)\n");
    printf("-t <second>   timeout for an http request (default: 1)\n");
    printf("-c <charset>  add key charset type (default: utf-8)\n");
	printf("-d            run as a daemon\n");
    printf("-s <file>     bak add or del key to file\n");
    printf("-h            print this help and exit\n");
    return;
}
int main(int argc, char **argv)
{
	int c;
	int daemon = 0;
	settings_init();
	queue_init();

    /* process arguments */
    while ((c = getopt(argc, argv, "l:p:t:c:s:dh")) != -1) {
        switch (c) {
        case 'l':
            settings.interface = strdup(optarg);
            break;
        case 'p':
            settings.port = atoi(optarg);
            break;
        case 't':
            settings.timeout = atoi(optarg);
            break;			
        case 'd':
            daemon = 1;
            break;
		case 'c':
			if(strcmp(optarg, "utf-8") == 0) {
				strcpy(settings.charset, "utf-8");
			} else {
				strcpy(settings.charset, "gbk");
			}	
            break;
		case 's':
			if(isExist(optarg) == 0) {
				if(createFile(optarg) == 0) {
					fprintf(stderr, "create store file faile");
					break;
				}
			}
			settings.isstore = 1;
			settings.storefile = strdup(optarg);
            break;
		case 'h':
        default:
			usage();
            return 1;
        }
    }

	if (daemon == 1){
        pid_t pid;

        /* Fork off the parent process */       
        pid = fork();
        if (pid < 0) {
                exit(EXIT_FAILURE);
        }
        /* If we got a good PID, then
           we can exit the parent process. */
        if (pid > 0) {
                exit(EXIT_SUCCESS);
        }
	}
	
	/* 忽略Broken Pipe信号 */
	signal (SIGPIPE,SIG_IGN);
	
	/* 处理kill信号 */
	signal (SIGINT, kill_signal);
	signal (SIGKILL, kill_signal);
	signal (SIGQUIT, kill_signal);
	signal (SIGTERM, kill_signal);
	signal (SIGHUP, kill_signal);
	
    struct evhttp *httpd;

    event_init();
    httpd = evhttp_start(settings.interface, settings.port);
	evhttp_set_timeout(httpd, settings.timeout);
	
    evhttp_set_gencb(httpd, httpban_handler, NULL);

    event_dispatch();

    evhttp_free(httpd);

    return 0;
}
